" ----------------------------
" important map
" ----------------------------
nnoremap <C-m> <Tab>
nnoremap gQ gw
xnoremap gQ gw
nnoremap <M-A> ggVG
" remap for cusor move insert mode
inoremap <M-l> <Right>
inoremap <M-h> <Left>
inoremap <M-j> <Down>
inoremap <M-k> <Up>
" map W/B
nnoremap <M-b> b
xnoremap <M-b> b
inoremap <M-b> <C-o>b
cnoremap <M-b> <C-left>
nnoremap <M-f> e
xnoremap <M-f> e
inoremap <M-f> <C-o>e<Right>
cnoremap <M-f> <C-right>
" save
nnoremap <M-s> :w!<Cr>
onoremap <M-s> :w!<Cr>
cnoremap <M-s> w!<Cr>
inoremap <M-s> <C-o>:w!<Cr>
xnoremap <M-s> <ESC>:w!<Cr>
nnoremap <M-S> :wa!<Cr>
cnoremap <M-S> wa!<Cr>
inoremap <M-S> <C-o>:wa!<Cr>
xnoremap <M-S> <ESC>:wa!<Cr>
" buffers mark messages
nnoremap <leader>b :ls<Cr>:b<Space>
nnoremap <leader><leader> <C-^>
" map to <esc>
inoremap <M-q> <ESC>
xnoremap <M-q> <ESC>
cnoremap <M-q> <ESC>
inoremap <M-w> <ESC>
xnoremap <M-w> <ESC>
cnoremap <M-w> <ESC>
" delete tmp files
if WINDOWS()
    nnoremap <leader>x :!powershell <C-r>=Expand("~/_leovim.clean.cmd")<Cr><Cr> \| e %<Cr><C-o>
else
    nnoremap <leader>x :!bash <C-r>=Expand("~/.leovim.clean")<Cr><Cr> \| e %<Cr><C-o>
endif
" ----------------------------
" functions
" ----------------------------
function! HasBackSpace() abort
    let col = col('.') - 1
    return !col || getline('.')[col - 1] =~ '\s'
endfunction
function! FileReadonly()
    return &readonly && &filetype !=# 'help' ? 'RO' : ''
endfunction
function! Execute(cmd)
    if exists("*execute")
        return execute(a:cmd)
    else
        redir => output
        silent! execute a:cmd
        redir END
        return output
    endif
endfunction
" --------------------------
" python_support
" --------------------------
function! s:get_python_path()
    if executable('python')
        return exepath('python')
    elseif executable('python3')
        return exepath('python3')
    elseif executable('python2')
        return exepath('python2')
    else
        return ""
    endif
endfunction
if has('nvim')
    let g:python3_host_prog = get(g:, 'python3_host_prog', s:get_python_path())
endif
function! s:get_python_version()
    if CYGWIN()
        return 0
    endif
    try
        let py_version = Execute('py3 print(sys.version)')
    catch /.*/
        try
            let py_version = Execute('py print(sys.version)')
        catch /.*/
            return 0
        endtry
    endtry
    let pyx_version_raw = matchstr(py_version, '\v\zs\d{1,}.\d{1,}.\d{1,}\ze')
    if pyx_version_raw == ''
        return 0
    endif
    let pyx_version = StringToFloat(pyx_version_raw, 2)
    if pyx_version > 3
        try
            execute("py3 import pygments")
            let g:pygments_import = get(g:, 'pygments_import', 1)
        catch /.*/
            let g:pygments_import = get(g:, 'pygments_import', 0)
        endtry
        if !has('nvim')
            let g:python3_host_prog = Trim(Execute('py3 print(sys.executable.strip())'))
        endif
    endif
    return pyx_version
endfunction
let g:python_version = s:get_python_version()
let g:python_path = s:get_python_path()
" --------------------------
" has_terminal
" --------------------------
if exists(':tnoremap')
    if has('patch-8.1.1')
        set termwinkey=<C-_>
        let g:has_terminal = 2
    else
        let g:has_terminal = 1
    endif
else
    let g:has_terminal = 0
endif
if has("popupwin") || exists('*nvim_open_win')
    let g:has_popup_floating = 1
else
    let g:has_popup_floating = 0
endif
" ------------------------
" has_truecolor
" ------------------------
if has('termguicolors') || WINDOWS() || HAS_GUI()
    try
        set termguicolors
        hi LineNr ctermbg=NONE guibg=NONE
        if !has('nvim')
            let &t_8f = "\<Esc>[38;2;%lu;%lu;%lum"
            let &t_8b = "\<Esc>[48;2;%lu;%lu;%lum"
        endif
        let g:has_truecolor = 1
        nnoremap <M-k>g :set notermguicolors! notermguicolors?<Cr>
    catch
        let g:has_truecolor = 0
    endtry
else
    let g:has_truecolor = 0
endif
" -----------------------------------
" git version
" -----------------------------------
if executable('git')
    let s:git_version_raw = matchstr(system('git --version'), '\v\zs\d{1,4}.\d{1,4}.\d{1,4}\ze')
    let g:git_version = StringToFloat(s:git_version_raw)
else
    let g:git_version = 0
endif
" ------------------------------
" node install tool
" ------------------------------
if executable('node') && executable('npm')
    let s:node_version_raw = matchstr(system('node --version'), '\vv\zs\d{1,4}.\d{1,4}\ze')
    let g:node_version = StringToFloat(s:node_version_raw)
else
    let g:node_version = 0
endif
" --------------------------
" set PATH && term
" --------------------------
if WINDOWS()
    if get(g:,'leovim_loaded',0) == 0
        if isdirectory($HOME . "\\.leovim.windows")
            let $PATH = $HOME . "\\.leovim.windows\\cppcheck;" . $PATH
            let $PATH = $HOME . "\\.leovim.windows\\gtags\\bin;" . $PATH
            let $PATH = $HOME . "\\.leovim.windows\\tools;" . $PATH
        endif
    endif
    set winaltkeys=no
    if HAS_GUI()
        set lines=999
        set columns=999
    endif
    if has('libcall') && !has('nvim') && HAS_GUI()
        let g:gvimfullscreendll = $HOME ."\\.leovim.windows\\tools\\gvimfullscreen.dll"
        function! ToggleFullScreen()
            call libcallnr(g:gvimfullscreendll, "ToggleFullScreen", -1)
        endfunction
        nnoremap <C-cr> <ESC>:call ToggleFullScreen()<Cr>
        let g:VimAlpha = 255
        function! SetAlpha(alpha)
            let g:VimAlpha = g:VimAlpha + a:alpha
            if g:VimAlpha < 95
                let g:VimAlpha = 95
            endif
            if g:VimAlpha > 255
                let g:VimAlpha = 255
            endif
            call libcall(g:gvimfullscreendll, 'SetAlpha', g:VimAlpha)
        endfunction
        nnoremap <silent><M-\>  :call SetAlpha(5)<Cr>
        nnoremap <silent><M-\|> :call SetAlpha(-5)<Cr>
    endif
else
    if get(g:, 'leovim_loaded', 0) == 0 && isdirectory($HOME . "/.leovim.unix")
        if LINUX()
            let $PATH = $HOME . "/.leovim.unix/linux:" . $PATH
        elseif MACOS()
            let $PATH = $HOME . "/.leovim.unix/macos:" . $PATH
        endif
    endif
    " --------------------------
    " terminal comparability
    " --------------------------
    set t_ut=
    if exists('+t_TI') && exists('+t_TE')
        let &t_TI = ''
        let &t_TE = ''
    endif
    if exists('+t_RS') && exists('+t_SH')
        let &t_RS = ''
        let &t_SH = ''
    endif
    if has('nvim') && $TMUX != ''
        let $TERM = "xterm-256color"
    elseif HAS_GUI() == 0 && !has('nvim')
        if $TMUX != ''
            try
                set term=xterm-256color
            catch
                set term=$TERM
            endtry
        else
            set term=$TERM
        endif
    endif
endif
" ------------------------------
" tags conf
" ------------------------------
if Require('notags')
    let g:ctags_type = ''
    let g:gtags_version = 0
elseif WINDOWS() && Require('tags') || UNIX()
    if WINDOWS() && filereadable(Expand("~/.leovim.windows/tools/ctags.exe"))
        let g:ctags_type = 'Universal-json'
    elseif executable('ctags')
        let g:ctags_type = split(system('ctags --version'), ' ')[0]
        if g:ctags_type =~ 'Universal'
            if system('ctags --list-features | grep json') =~ 'json'
                let g:ctags_type = 'Universal-json'
            else
                let g:ctags_type = 'Universal'
            endif
        endif
    else
        let g:ctags_type = ''
    endif
    if WINDOWS()
        let $GTAGSCONF = Expand($HOME . "/.leovim.windows/gtags/share/gtags/gtags.conf")
    endif
    if executable('gtags') && get(g:, 'ctags_type', '') != '' && exists('GTAGSCONF') && filereadable($GTAGSCONF)
        let s:gtags_version = matchstr(system('gtags --version'), '\v\zs\d{1,2}.\d{1,2}.\d{1,2}\ze')
        let g:gtags_version = StringToFloat(s:gtags_version, 2)
        if get(g:, 'pygments_import', 0)
            let $GTAGSLABEL = 'native-pygments'
        else
            let $GTAGSLABEL = 'native'
        endif
    else
        let g:gtags_version = 0
    endif
else
    let g:ctags_type = ''
    let g:gtags_version = 0
endif
" ------------------------
" set tab label
" ------------------------
function! Vim_NeatBuffer(bufnr, fullname)
    let l:name = bufname(a:bufnr)
    if getbufvar(a:bufnr, '&modifiable')
        if l:name == ''
            return '[No Name]'
        else
            if a:fullname
                return fnamemodify(l:name, ':p')
            else
                return fnamemodify(l:name, ':t')
            endif
        endif
    else
        let l:buftype = getbufvar(a:bufnr, '&buftype')
        if l:buftype == 'quickfix'
            return '[Quickfix]'
        elseif l:name != ''
            if a:fullname
                return '-'.fnamemodify(l:name, ':p')
            else
                return '-'.fnamemodify(l:name, ':t')
            endif
        else
            return '[No Name]'
        endif
    endif
endfunc
" get a single tab label
function! Vim_NeatTabLabel(n)
    let l:buflist = tabpagebuflist(a:n)
    let l:winnr = tabpagewinnr(a:n)
    let l:bufnr = l:buflist[l:winnr - 1]
    return Vim_NeatBuffer(l:bufnr, 0)
endfun
" make tabline in terminal mode
function! Vim_NeatTabLine()
    let s = ''
    for i in range(tabpagenr('$'))
        " select the highlighting
        if i + 1 == tabpagenr()
            let s .= '%#TabLineSel#'
        else
            let s .= '%#TabLine#'
        endif
        " set the tab page number (for mouse clicks)
        let s .= '%' . (i + 1) . 'T'
        " the label is made by MyTabLabel()
        let s .= ' %{Vim_NeatTabLabel(' . (i + 1) . ')} '
    endfor
    " after the last tab fill with TabLineFill and reset tab page nr
    let s .= '%#TabLineFill#%T'
    " right-align the label to close the current tab page
    if tabpagenr('$') > 1
        let s .= '%=%#TabLine#%999XX'
    endif
    return s
endfunction
" get a single tab label in gui
function! Vim_NeatGuiTabLabel()
    let l:num = v:lnum
    let l:buflist = tabpagebuflist(l:num)
    let l:winnr = tabpagewinnr(l:num)
    let l:bufnr = l:buflist[l:winnr - 1]
    return Vim_NeatBuffer(l:bufnr, 0)
endfunc
" set label && tabline
set guitablabel=%{Vim_NeatGuiTabLabel()}
set tabline=%!Vim_NeatTabLine()
" -----------------------------------------------------------
" pack_tool
" -----------------------------------------------------------
let g:plug_threads = get(g:, 'plug_threads', 8)
function! s:plug_add(plugin, ...)
    " delete last / or \
    let plugin = substitute(a:plugin, '[\/]\+$', '', 'g')
    let pack = split(plugin, '\/')[1]
    if has_key(g:leovim_installed, pack)
        return
    else
        if a:0 == 0
            call plug#(plugin)
        else
            call plug#(plugin, a:1)
        endif
        if a:0 >0 && has_key(a:1, 'as') && a:1['as'] != ''
            let pack = tolower(a:1['as'])
        else
            let pack = tolower(pack)
        endif
    endif
    let g:leovim_installed[pack] = 0
endfunction
command! -nargs=+ PlugAdd call <sid>plug_add(<args>)
" ------------------------------
" install packs
" ------------------------------
set rtp^=$VIM_PATH
let $DEP_PATH = Expand("~/.leovim.d")
let opt_path = Expand("$DEP_PATH/pack/add/opt")
call plug#begin(opt_path)
if filereadable(expand("$DEP_PATH/pack.vim"))
    source ~/.leovim.d/pack.vim
endif
for vim in split(glob("$ADD_PATH/*.vim"), "\n")
    exec "source " . vim
endfor
noremap <silent><leader>U :PlugUpdate<Cr>
call plug#end()
" ------------------------------
" set installed
" ------------------------------
for [plug, value] in items(g:plugs)
    let dir = value['dir']
    if isdirectory(dir)
        let g:leovim_installed[tolower(plug)] = 1
    endif
endfor
" ------------------------------
" set $PATH
" ------------------------------
let mason_bin = expand('~/.leovim.d/mason/bin')
if g:complete_engine != 'cmp' && isdirectory(mason_bin)
    if WINDOWS()
        let $PATH = mason_bin . ';' . $PATH
    else
        let $PATH = mason_bin . ':' . $PATH
    endif
endif
" ------------------------------
" source setcfg
" ------------------------------
for vim in split(glob("$SET_PATH/*.vim"), "\n")
    exec "source " . vim
endfor
" ------------------------
" open config file
" ------------------------
nnoremap <M-h><Cr> :source ~/.leovim/boostup/init.vim<Cr>
nnoremap <M-h>o :tabe ~/.vimrc.opt<Cr>
nnoremap <M-h>p :tabe ~/.leovim/pack
nnoremap <M-h>P :tabe ~/.leovim.d/pack.vim<Cr>
nnoremap <M-h>A :tabe ~/.leovim.d/after.vim<Cr>
" set filetype unix and trim \r
nnoremap <M-h>u :set ff=unix<Cr>:%s/\r//g<Cr>
function! TabeOpen(f) abort
    let f = expand(a:f)
    exec "tabe " . f
endfunction
nnoremap <silent><M-h>i :call TabeOpen("$BOOSTUP_PATH/init.vim")<Cr>
nnoremap <silent><M-h>m :call TabeOpen("$STD_PATH/main.vim")<Cr>
nnoremap <silent><M-h>e :call TabeOpen("$VIM_PATH/entry.vim")<Cr>
nnoremap <silent><M-h>b :call TabeOpen("$ADD_PATH/basement.vim")<Cr>
nnoremap <silent><M-h>f :call TabeOpen("$SET_PATH/finder.vim")<Cr>
nnoremap <silent><M-h>k :call TabeOpen("$CFG_PATH/keybindings.json")<Cr>
nnoremap <silent><M-h>v :call TabeOpen("$CFG_PATH/vscode.vim")<Cr>
nnoremap <silent><M-h>F :call TabeOpen("$CFG_PATH/fzf.vim")<Cr>
nnoremap <silent><M-h>l :call TabeOpen("$LUA_PATH/lsp.lua")<Cr>

" ------------------------
" open in vscode
" ------------------------
if exists("g:vscode_keybindings_dir") && isdirectory(g:vscode_keybindings_dir)
    function s:copykeybindings() abort
        if WINDOWS()
            let template = '!xcopy %s %s /Y'
            let cmd = printf(template, $CFG_PATH . '\keybindings.json', g:vscode_keybindings_dir)
        else
            let template = '!cp -f %s %s'
            let cmd = printf(template, $CFG_PATH . '/keybindings.json', g:vscode_keybindings_dir)
        endif
        execute(cmd)
    endfunction
    command! CopyKeybindings call s:copykeybindings()
    nnoremap <M-h>K :CopyKeybindings<Cr>
endif
function! s:get_cursor_pos(text, col)
    " Find the start location
    let col = a:col
    while col >= 0 && a:text[col] =~ '\f'
        let col = col - 1
    endwhile
    let col = col + 1
    " Match file name and position
    let m = matchlist(a:text, '\v(\f+)%([#:](\d+))?%(:(\d+))?', col)
    if len(m) > 0
        return [m[1], m[2], m[3]]
    endif
    return []
endfunc
function! OpenFileLinkInEditor(editor, text, col)
    let location = s:get_cursor_pos(a:text, a:col)
    if a:editor == 'code'
        let editor = 'code --goto'
    else
        let editor = a:editor
    endif
    " location 0: file, 1: row, 2: column
    if location[0] != ''
        if location[1] != ''
            if location[2] != ''
                if editor =~ 'code'
                    let command = editor . " " . location[0] . ":" . str2nr(location[1]) . ":" . str2nr(location[2])
                else
                    let command = editor . " --column " . str2nr(location[2]) . " " . location[0] . ":" . str2nr(location[1])
                endif
                if Installed('asyncrun.vim')
                    exec "AsyncRun -silent " . command
                else
                    exec "! " . command
                endif
            else
                let command = editor . " " . location[0] . ":" . str2nr(location[1])
                if Installed('asyncrun.vim')
                    exec "AsyncRun -silent " . command
                else
                    exec "! " . command
                endif
            endif
        else
            let command = editor . " " . location[0]
            if Installed('asyncrun.vim')
                exec "AsyncRun -silent " . command
            else
                exec "! " . command
            endif
        endif
    else
        echo "Not a valid file path"
    endif
endfunc
if executable('code')
    function! s:open_in_vscode()
        if Installed('asyncrun.vim')
            let cmd = printf("AsyncRun! code --goto %s:%d", expand("%:p"), line("."))
        else
            let cmd = printf("!code --goto %s:%d", expand("%:p"), line("."))
        endif
        silent! exec cmd
    endfunction
    command! OpenInVSCode call s:open_in_vscode()
    nnoremap <silent><M-j>o :OpenInVSCode<Cr>
    " NOTE: open file under line in vscode
    command! OpenFileLinkInVSCode call OpenFileLinkInEditor("code", getline("."), col("."))
    nnoremap <silent><M-j>f :OpenFileLinkInVSCode<cr>
endif
" --------------------------
" open other ides config
" --------------------------
nnoremap <M-h>V :tabe ~/.leovim/msvc/vs.vim<Cr>
nnoremap <M-h>I :tabe ~/.leovim/jetbrains/idea.vim<Cr>
" --------------------------
" open or add file
" --------------------------
function! s:open_or_create_file(file, ...) abort
    let file = Expand(a:file)
    if filereadable(file)
        try
            execute "tabe " . file
            return 1
        catch /.*/
            call preview#errmsg("Could not open file " . a:file . "!")
            return 0
        endtry
    else
        let dir = FileDir(file)
        try
            if !isdirectory(dir)
                call mkdir(dir, "p")
            endif
            let content = []
            for each in a:000
                if type(each) == v:t_list
                    let content += each
                elseif type(each) == v:t_dict
                    let content += keys(each)
                elseif type(each) == v:t_number
                    call add(content, string(num))
                elseif type(each) == v:t_string
                    call add(content, each)
                elseif index([v:t_func, v:t_job, v:t_none, v:t_channel]) < 0
                    call add(content, string(each))
                endif
            endfor
            if len(content) > 0
                call writefile(content, file)
            endif
            execute "tabe " . file
            return 1
        catch /.*/
            call preview#errmsg("Could not create or write to file " . a:file . "!")
            return 0
        endtry
    endif
endfunction
" open todo
function! s:open_todo() abort
    if !empty(GitRootPath())
        let root_path = substitute(GitRootPath(), "\\", "/", "g")
        let todo_md = root_path . "/TODO.md"
        let opened = s:open_or_create_file(todo_md, ["## TODO:", "- [ ]"])
    else
        let opened = 0
    endif
    if !opened
        call preview#errmsg('OpenTODO must be executed in a git repo.')
    endif
endfunction
command! OpenTODO call s:open_todo()
nnoremap <M-h>t :OpenTODO<Cr>
" open readme
function! s:open_readme() abort
    if !empty(GitRootPath())
        let root_path = substitute(GitRootPath(), "\\", "/", "g")
        let readme_md = root_path . "/README.md"
        let opened = s:open_or_create_file(readme_md, ["# README"])
    else
        let opened = 0
    endif
    if !opened
        call preview#errmsg('OpenREADME must be executed in a git repo.')
    endif
endfunction
command! OpenREADME call s:open_readme()
nnoremap <M-h>r :OpenREADME<Cr>
" gitconfig
command! OpenGitConfig call <SID>open_or_create_file("~/.gitconfig",
            \ ["[alias]",
            \ "  lg = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative",
            \ "  st = status",
            \ "  co = checkout",
            \ "  ci = commit",
            \ "  br = branch"
            \ ])
nnoremap <M-h>g :OpenGitConfig<Cr>
" ssh/config
command! OpenSSHConfig call <SID>open_or_create_file("~/.ssh/config")
nnoremap <M-h>c :OpenSSHConfig<Cr>
" bashrc
command! OpenBashrc call <SID>open_or_create_file("~/.bashrc")
nnoremap <M-h>B :OpenBashrc<Cr>
command! OpenConfigrc call <SID>open_or_create_file("~/.configrc")
nnoremap <M-h>C :OpenConfigrc<Cr>
if filereadable(expand("~/.leovim.d/after.vim"))
    source ~/.leovim.d/after.vim
endif
